#include "vsf.h"

#if defined(BCMWIFI_SDIO) || defined(BCMWIFI_SPI)

enum bcm_wifi_EVT_t
{
	BCMWIFI_EVT_ASYNC = VSFSM_EVT_USER_LOCAL_INSTANT + 0,
	BCMWIFI_EVT_ASYNC_TO = VSFSM_EVT_USER_LOCAL_INSTANT + 1,
	
	BCMWIFI_EVT_REENTRY = VSFSM_EVT_USER_LOCAL + 0,
	BCMWIFI_EVT_DELAY = VSFSM_EVT_USER_LOCAL + 1,
};

static vsf_err_t bcm_wifi_init(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfip_netif_t *netif = (struct vsfip_netif_t *)pt->user_data;
	struct bcm_wifi_t *bcm_wifi = (struct bcm_wifi_t *)netif->drv->param;
	struct vsfip_buffer_t *ioctrl_reply;
	uint32_t *data;
	vsf_err_t err;
	
	vsfsm_pt_begin(pt);
	
	bcm_wifi->join_status[0] = 0;
	bcm_wifi->join_status[1] = 0;
	bcm_wifi->join_status[2] = 0;
	
	bcm_bus_construct(&bcm_wifi->bus);
	bcm_wifi->init_pt.sm = pt->sm;
	bcm_wifi->init_pt.user_data = (void *)&bcm_wifi->bus;
	bcm_wifi->retry = 0;
	do
	{
		bcm_wifi->init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_bus_init(&bcm_wifi->init_pt, evt);
		if (err > 0) 
			return err;
		else if (err == 0)
			break;
	} while (++bcm_wifi->retry < 3);
	if (err < 0)
		return err;
	
	bcm_sdpcm_init(bcm_wifi);
	
	// ioctrl
	bcm_wifi->init_pt.user_data = bcm_wifi;
	
	// turn off SDPCM TX glomming
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 4,
													BCMIOVAR_TX_GLOM);
	if (NULL == data) return VSFERR_FAIL;
	SET_LE_U32((uint8_t *)data, 0);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// turn APSTA on
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 4,
													BCMIOVAR_APSTA);
	if (NULL == data) return VSFERR_FAIL;
	SET_LE_U32((uint8_t *)data, 1);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set AMPDU paramters
#if BCM_CHIP_BCM43362A2
	// set AMPDU block ACK window size
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 4,
													BCMIOVAR_AMPDU_BA_WSIZE);
	if (NULL == data) return VSFERR_FAIL;
	SET_LE_U32((uint8_t *)data, 8);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set number of MPDUs available for AMPDU
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 4,
													BCMIOVAR_AMPDU_MPDU);
	if (NULL == data) return VSFERR_FAIL;
	SET_LE_U32((uint8_t *)data, 4);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set size of advertised receive AMPDU
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 4,
													BCMIOVAR_AMPDU_RX_FACTOR);
	if (NULL == data) return VSFERR_FAIL;
	SET_LE_U32((uint8_t *)data, 0);		// AMPDU_RX_FACTOR_8K
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
#endif
	
	// send set country command
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 12,
													BCMIOVAR_COUNTRY);
	if (NULL == data) return VSFERR_FAIL;
	memset((void*)data, 0, 12);
	memcpy(&data[0], bcm_wifi->country, 2);
	memcpy(&data[2], bcm_wifi->country, 2);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	vsfsm_pt_delay(pt, 10);
	
	// set event mask
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 16,
													BCMIOVAR_EVT_MSGS);
	if (NULL == data) return VSFERR_FAIL;
	memset((void*)data, 0, 16);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0)
	{
		vsfip_buffer_release(bcm_wifi->buffer);
		return VSFERR_FAIL;
	}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// send UP command
	bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 0);
	if (NULL == bcm_wifi->buffer) return VSFERR_FAIL;
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_UP,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set the GMode
	data = (uint32_t *)bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 4);
	if (NULL == bcm_wifi->buffer) return VSFERR_FAIL;
	SET_LE_U32((uint8_t *)data, 1);			// GMODE_AUTO
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_GMODE,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// read mac
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 6,
													BCMIOVAR_ETHADDR);
	if (NULL == data) return VSFERR_FAIL;
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_GET, BCMIOCTRL_GET_VAR,
							bcm_wifi->buffer, &ioctrl_reply);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	if ((ioctrl_reply != NULL) && (ioctrl_reply->buf.size > 6))
	{
		netif->macaddr.size = 6;
		memcpy(netif->macaddr.addr.s_addr_buf, ioctrl_reply->buf.buffer,
					netif->macaddr.size);
	}
	else
	{
		netif->macaddr.size = 0;
	}
	vsfip_buffer_release(ioctrl_reply);
	
	netif->mac_broadcast.size = 6;
	memset(netif->mac_broadcast.addr.s_addr_buf, 0xFF, 6);
	netif->mtu = 1500;
	netif->drv->netif_header_size = VSFIP_ETH_HEADSIZE;
	netif->drv->hwtype = VSFIP_ETH_HWTYPE;
	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

static vsf_err_t bcm_wifi_fini(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct vsfip_netif_t *netif = (struct vsfip_netif_t *)pt->user_data;
	struct bcm_wifi_t *bcm_wifi = (struct bcm_wifi_t *)netif->drv->param;
	vsf_err_t err;
	
	vsfsm_pt_begin(pt);
	
	// send DOWN command
	bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 0);
	if (NULL == bcm_wifi->buffer) return VSFERR_FAIL;
	bcm_wifi->init_pt.user_data = bcm_wifi;
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_DOWN,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	bcm_sdpcm_fini(bcm_wifi);
	
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_bus_fini(&bcm_wifi->init_pt, evt);
	if (err != 0) return err;
	
	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

static struct vsfip_netdrv_op_t bcm_wifi_netdrv_op =
{
	bcm_wifi_init, bcm_wifi_fini, NULL, bcm_sdpcm_header_sta, vsfip_eth_available
};

vsf_err_t bcm_wifi_netif_init(struct bcm_wifi_t *bcm_wifi)
{
	bcm_wifi->netdrv.op = &bcm_wifi_netdrv_op;
	bcm_wifi->netdrv.param = bcm_wifi;
	bcm_wifi->netif.drv = &bcm_wifi->netdrv;

#if defined(BCMWIFI_SDIO) || defined(BCMWIFI_SPI)
	switch (bcm_wifi->bus.type)
	{
#ifdef BCMWIFI_SPI
	case BCM_BUS_TYPE_SPI:
		bcm_wifi->bus.op = &bcm_bus_spi_op;
		break;
#endif
#ifdef BCMWIFI_SDIO
	case BCM_BUS_TYPE_SDIO:
		bcm_wifi->bus.op = &bcm_bus_sdio_op;
		break;
#endif
	}
#endif
	
	return VSFERR_NONE;
}

// WIFI operations
struct bcm_ssid_t
{
	uint32_t ssid_len;
	uint8_t ssid[32];
};
struct bcm_scan_param_t
{
	uint32_t version;
	uint16_t action;
	uint16_t syncid;
	
	struct bcm_ssid_t ssid;
	uint8_t bssid[6];
	int8_t bss_type;
	int8_t scan_type;
	int32_t nprobes;
	int32_t active_time;
	int32_t passive_time;
	int32_t home_time;
	int32_t channel_num;
	uint16_t channel_list[1];
};
static const uint16_t bcm_wifi_chlist[] = {1,2,3,4,5,6,7,8,9,10,11,12,13,14};
vsf_err_t bcm_wifi_scan(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
						struct vsfip_buffer_t **result, uint8_t interface)
{
	struct bcm_wifi_t *bcm_wifi = (struct bcm_wifi_t *)pt->user_data;
	uint32_t *data;
	uint8_t *evtmask;
	struct bcm_scan_param_t *scan_param;
	struct bcm_sdpcm_evthead_t *event;
	vsf_err_t err;
	int i;
	
	if (result != NULL)
	{
		*result = NULL;
	}
	
	vsfsm_pt_begin(pt);
	
	bcm_wifi->init_pt.user_data = bcm_wifi;
	
	// enable the async by set the corresponding event mask bit(s)
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer,
						17 + 4, BCMIOVAR_BSSCFG_EVT_MSGS);
	if (NULL == data) return VSFERR_FAIL;
	data[0] = SYS_TO_LE_U32(interface);
	evtmask = (uint8_t *)&data[1];
	memset(evtmask, 0, BCMASYNC_EVT_MASKLEN);
	evtmask[BCMASYNC_EVT_SCAN_RESULT / 8] |= 1 << (BCMASYNC_EVT_SCAN_RESULT % 8);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// send escan
	scan_param = (struct bcm_scan_param_t *)bcm_sdpcm_get_iovar_buffer(
				&bcm_wifi->buffer,
				sizeof(struct bcm_scan_param_t) + sizeof(bcm_wifi_chlist) - 2,
				BCMIOVAR_ESCAN);
	if (NULL == scan_param) return VSFERR_NONE;
	memset(scan_param, 0, sizeof(struct bcm_scan_param_t));
	scan_param->version = SYS_TO_LE_U32(BCM_WIFI_ESCAN_VER);
	scan_param->action = SYS_TO_LE_U16(BCM_WIFI_SCANACT_START);
	memset(scan_param->bssid, 0xFF, sizeof(scan_param->bssid));
	scan_param->bss_type = BCM_WIFI_BSSTYPE_ANY;
	scan_param->scan_type = BCM_WIFI_SCANTYPE_ACTIVE;
	scan_param->nprobes = -1;
	scan_param->active_time = -1;
	scan_param->passive_time = -1;
	scan_param->home_time = -1;
	scan_param->channel_num = dimof(bcm_wifi_chlist);
	for (i = 0; i < dimof(bcm_wifi_chlist); i++)
	{
		scan_param->channel_list[i] = SYS_TO_LE_U16(bcm_wifi_chlist[i]);
	}
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, BCM_INTERFACE_STA,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	vsfsm_sem_init(&bcm_wifi->sdpcm.async_sem, 0, BCMWIFI_EVT_ASYNC);
	bcm_wifi->sdpcm.async_pending = true;
	
	while (1)
	{
		if (vsfsm_sem_pend(&bcm_wifi->sdpcm.async_sem, pt->sm))
		{
			vsfsm_pt_wfe(pt, BCMWIFI_EVT_ASYNC);
		}
		
		// process async
		if (result != NULL)
		{
			event = (struct bcm_sdpcm_evthead_t *)\
							bcm_wifi->sdpcm.async_evt->buf.buffer;
			
			if (BCMASYNC_EVTSTATUS_SUCCESS == event->status)
			{
				bcm_wifi->sdpcm.async_pending = false;
				vsfip_buffer_release(bcm_wifi->sdpcm.async_evt);
				bcm_wifi->sdpcm.async_evt = NULL;
				break;
			}
			else if ((BCMASYNC_EVTSTATUS_FAIL == event->status) ||
						(BCMASYNC_EVTSTATUS_TIMEOUT == event->status))
			{
				bcm_wifi->sdpcm.async_pending = false;
				vsfip_buffer_release(bcm_wifi->sdpcm.async_evt);
				bcm_wifi->sdpcm.async_evt = NULL;
				return VSFERR_FAIL;
			}
			else if ((BCMASYNC_EVTSTATUS_NEWSCAN == event->status) ||
						(BCMASYNC_EVTSTATUS_NEWASSOC == event->status) ||
						(BCMASYNC_EVTSTATUS_ABORT == event->status) ||
						(event->status != BCMASYNC_EVTSTATUS_PARTIAL))
			{
				vsfip_buffer_release(bcm_wifi->sdpcm.async_evt);
				bcm_wifi->sdpcm.async_evt = NULL;
			}
			else
			{
				bcm_wifi->sdpcm.async_evt->buf.buffer +=
										sizeof(struct bcm_sdpcm_evthead_t);
				bcm_wifi->sdpcm.async_evt->buf.size -=
										sizeof(struct bcm_sdpcm_evthead_t);
				*result = bcm_wifi->sdpcm.async_evt;
				bcm_wifi->sdpcm.async_evt = NULL;
				
				vsfsm_post_evt_pending(pt->sm, BCMWIFI_EVT_REENTRY);
				vsfsm_pt_wfe(pt, BCMWIFI_EVT_REENTRY);
			}
		}
		else
		{
			vsfip_buffer_release(bcm_wifi->sdpcm.async_evt);
			bcm_wifi->sdpcm.async_evt = NULL;
		}
	}
	
	vsfsm_pt_end(pt);
	
	return VSFERR_NONE;
}

#if BCM_CHIP_BCM43362A2
struct bcm_wifi_wsec_pmk_t
{
	uint16_t keylen;
	uint16_t flags;
	uint8_t key[64];
};
struct bcm_wifi_channel_info_t
{
	uint32_t hw_channel;
	uint32_t target_channel;
	uint32_t scan_channel;
};
struct bcm_wifi_ssid_t
{
	uint32_t len;
	uint8_t ssid[32];
};
#endif

static vsf_err_t bcm_wifi_prepare_join(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
			uint32_t interface, enum bcm_authtype_t authtype,
			const uint8_t *key, uint8_t keylen)
{
	struct bcm_wifi_t *bcm_wifi = (struct bcm_wifi_t *)pt->user_data;
	struct bcm_wifi_wsec_pmk_t *psk;
	uint32_t tmp32;
	uint32_t *data;
	uint8_t *evtmask;
	vsf_err_t err;
	
	vsfsm_pt_begin(pt);
	if (/*((keylen > 64) || (keylen < 8)) ||*/
		!((interface == BCM_INTERFACE_STA) ||
			((interface == BCM_INTERFACE_P2P) && (!bcm_wifi->p2p_isup))))
	{
		return VSFERR_FAIL;
	}
	
	bcm_wifi->join_status[interface] = 0;
	
	bcm_wifi->prepare_join_pt.sm = pt->sm;
	bcm_wifi->prepare_join_pt.user_data = bcm_wifi;
	
	// set security type
	data = (uint32_t *)bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 4);
	if (NULL == data) return VSFERR_FAIL;
	tmp32 = (authtype & 0xFF) & ~BCM_WIFI_AUTH_WPS_ENABLED;
	data[0] = SYS_TO_LE_U32(tmp32);
	bcm_wifi->prepare_join_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_WSEC,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set supplicant variable
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 8,
													BCMIOVAR_BSSCFG_SUP_WPA);
	if (NULL == data) return VSFERR_FAIL;
	data[0] = SYS_TO_LE_U32(interface);
	tmp32 = (((authtype & BCM_WIFI_AUTH_WPA_ENABLED) != 0) ||
					((authtype & BCM_WIFI_AUTH_WPA2_ENABLED) != 0)) ? 1 : 0;
	data[1] = SYS_TO_LE_U32(tmp32);
	bcm_wifi->prepare_join_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set EAPOL version
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 8,
											BCMIOVAR_BSSCFG_SUP_WPA2_EAPVER);
	if (NULL == data) return VSFERR_FAIL;
	data[0] = SYS_TO_LE_U32(interface);
	data[1] = SYS_TO_LE_U32((uint32_t)-1);
	bcm_wifi->prepare_join_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	if (authtype & (BCM_WIFI_AUTH_WPA_ENABLED | BCM_WIFI_AUTH_WPA2_ENABLED))
	{
		// delay 2ms
		vsfsm_pt_delay(pt, 2);
		
		psk = (struct bcm_wifi_wsec_pmk_t *)bcm_sdpcm_get_ioctrl_buffer(\
						&bcm_wifi->buffer, sizeof(struct bcm_wifi_wsec_pmk_t));
		if (NULL == psk) return VSFERR_FAIL;
		memset(psk, 0, sizeof(struct bcm_wifi_wsec_pmk_t));
		memcpy(psk->key, key, keylen);
		psk->keylen = keylen;
		psk->flags = 1 << 0;		// WSEC_PASSPHRASE
		bcm_wifi->prepare_join_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
								BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_SWEC_PMK,
								bcm_wifi->buffer, NULL);
		if (err > 0) return err;
		if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
		vsfip_buffer_release(bcm_wifi->buffer);
	}
	else if (authtype & BCM_WIFI_AUTH_WEP_ENABLED)
	{
		// TODO:
		return VSFERR_NOT_SUPPORT;
	}
	
	// set infrastructure mode
	data = (uint32_t *)bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 4);
	if (NULL == data) return VSFERR_FAIL;
	data[0] = SYS_TO_LE_U32(1);
	bcm_wifi->prepare_join_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_INFRA,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set auth type
	data = (uint32_t *)bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 4);
	if (NULL == data) return VSFERR_FAIL;
	tmp32 = (authtype == BCM_AUTHTYPE_WEP_SHARED) ? 1 : 0;
	data[0] = SYS_TO_LE_U32(tmp32);
	bcm_wifi->prepare_join_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_AUTH,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// set wpa auth mode
	data = (uint32_t *)bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 4);
	if (NULL == data) return VSFERR_FAIL;
	if (authtype & BCM_WIFI_AUTH_WPA_ENABLED)
	{
		data[0] = SYS_TO_LE_U32(BCM_WIFI_WPAAUTH_PSK);
	}
	else if (authtype & BCM_WIFI_AUTH_WPA2_ENABLED)
	{
		data[0] = SYS_TO_LE_U32(BCM_WIFI_WPA2AUTH_PSK);
	}
	else
	{
		data[0] = SYS_TO_LE_U32(BCM_WIFI_WPAAUTH_DISABLED);
		bcm_wifi->join_status[interface] |= BCM_WIFI_JOINSTATUS_SECURED;
	}
	bcm_wifi->prepare_join_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_WPA_AUTH,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	// enable the async by set the corresponding event mask bit(s)
	data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer,
						17 + 4, BCMIOVAR_BSSCFG_EVT_MSGS);
	if (NULL == data) return VSFERR_FAIL;
	data[0] = SYS_TO_LE_U32(interface);
	evtmask = (uint8_t *)&data[1];
	memset(evtmask, 0, BCMASYNC_EVT_MASKLEN);
	evtmask[BCMASYNC_EVT_SET_SSID / 8] |= 1 << (BCMASYNC_EVT_SET_SSID % 8);
	evtmask[BCMASYNC_EVT_LINK / 8] |= 1 << (BCMASYNC_EVT_LINK % 8);
	evtmask[BCMASYNC_EVT_AUTH / 8] |= 1 << (BCMASYNC_EVT_AUTH % 8);
	evtmask[BCMASYNC_EVT_DEAUTH_IND / 8] |= 1 << (BCMASYNC_EVT_DEAUTH_IND % 8);
	evtmask[BCMASYNC_EVT_DISASSOC_IND / 8] |= 1 << (BCMASYNC_EVT_DISASSOC_IND % 8);
	evtmask[BCMASYNC_EVT_PSK_SUP / 8] |= 1 << (BCMASYNC_EVT_PSK_SUP % 8);
	evtmask[BCMASYNC_EVT_ROAM / 8] |= 1 << (BCMASYNC_EVT_ROAM % 8);
	bcm_wifi->prepare_join_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->prepare_join_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	vsfsm_pt_end(pt);
	return VSFERR_NONE;
}

vsf_err_t bcm_wifi_join(struct vsfsm_pt_t *pt, vsfsm_evt_t evt,
			uint32_t *status, const char *ssid, enum bcm_authtype_t authtype,
			const uint8_t *key, uint8_t keylen)
{
	struct bcm_wifi_t *bcm_wifi = (struct bcm_wifi_t *)pt->user_data;
	struct bcm_wifi_ssid_t *ssid_info;
	struct bcm_sdpcm_evthead_t *event;
	uint32_t interface = BCM_INTERFACE_STA;
	uint32_t new_status, new_clr_status, tmp32;
	uint32_t *data;
	vsf_err_t err;
	bool complete;
	
	if (status != NULL)
	{
		*status = 0;
	}
	
	vsfsm_pt_begin(pt);
	
	bcm_wifi->init_pt.user_data = bcm_wifi;
	bcm_wifi->init_pt.sm = pt->sm;
	
	// set ack window size
#if BCM_CHIP_BCM43362A2
	if (!bcm_wifi->ap_isup)
	{
		data = (uint32_t *)bcm_sdpcm_get_iovar_buffer(&bcm_wifi->buffer, 4,
													BCMIOVAR_AMPDU_BA_WSIZE);
		if (NULL == data) return VSFERR_FAIL;
		tmp32 = (BCM_INTERFACE_STA == interface) ? 8 : 2;
		data[0] = SYS_TO_LE_U32(tmp32);
		bcm_wifi->init_pt.state = 0;
		vsfsm_pt_entry(pt);
		err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, interface,
								BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_VAR,
								bcm_wifi->buffer, NULL);
		if (err > 0) return err;
		if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
		vsfip_buffer_release(bcm_wifi->buffer);
	}
#endif
	
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_wifi_prepare_join(&bcm_wifi->init_pt, evt, interface, authtype,
								key, keylen);
	if (err != 0) return err;
	
	// join
	ssid_info = (struct bcm_wifi_ssid_t *)bcm_sdpcm_get_ioctrl_buffer(\
							&bcm_wifi->buffer, sizeof(struct bcm_wifi_ssid_t));
	if (NULL == ssid_info) return VSFERR_FAIL;
	memset(ssid_info, 0, sizeof(struct bcm_wifi_ssid_t));
	tmp32 = strlen(ssid);
	ssid_info->len = SYS_TO_LE_U32(tmp32);
	strcpy((char *)ssid_info->ssid, ssid);
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_SET_SSID,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	vsfsm_sem_init(&bcm_wifi->sdpcm.async_sem, 0, BCMWIFI_EVT_ASYNC);
	bcm_wifi->sdpcm.async_pending = true;
	
	while (1)
	{
		if (vsfsm_sem_pend(&bcm_wifi->sdpcm.async_sem, pt->sm))
		{
			vsfsm_pt_wfe(pt, BCMWIFI_EVT_ASYNC);
		}
		
		// process async
		event =
			(struct bcm_sdpcm_evthead_t *)bcm_wifi->sdpcm.async_evt->buf.buffer;
		
		complete = false;
		new_clr_status = 0;
		new_status = 0;
		if (event->ifidx != interface)
		{
			// do nothing
		}
		else if ((BCMASYNC_EVT_ROAM == event->evttype) &&
					(BCMASYNC_EVTSTATUS_SUCCESS == event->status))
		{
			new_status = BCM_WIFI_JOINSTATUS_LINKED;
		}
		else if (BCMASYNC_EVT_PSK_SUP == event->evttype)
		{
			if (BCMASYNC_SUPSTATUS_KEYED == event->status)
			{
				new_status = BCM_WIFI_JOINSTATUS_SECURED;
			}
			else if ((event->status != BCMASYNC_SUPSTATUS_KEYXCHANGE) &&
						(event->status != BCMASYNC_SUPSTATUS_LAST_BASIC_STATE))
			{
				// key failed
				new_status = BCM_WIFI_JOINSTATUS_SECURE_FAILED;
				complete = true;
			}
		}
		else if (BCMASYNC_EVT_SET_SSID == event->evttype)
		{
			if (BCMASYNC_EVTSTATUS_SUCCESS == event->status)
			{
				new_status = BCM_WIFI_JOINSTATUS_COMPLETED;
			}
			else if (BCMASYNC_EVTSTATUS_NO_NETWORKS == event->status)
			{
			}
			else
			{
				complete = true;
			}
		}
		else if (BCMASYNC_EVT_LINK == event->evttype)
		{
			if (event->flags & 1)
			{
				new_status = BCM_WIFI_JOINSTATUS_LINKED;
			}
			else
			{
				new_status = BCM_WIFI_JOINSTATUS_LINK_FAILED;
				new_clr_status = BCM_WIFI_JOINSTATUS_LINKED;
			}
		}
		else if ((BCMASYNC_EVT_DEAUTH_IND == event->evttype) ||
					(BCMASYNC_EVT_DISASSOC_IND == event->evttype))
		{
			new_status = BCM_WIFI_JOINSTATUS_AUTH_FAILED;
			new_clr_status = BCM_WIFI_JOINSTATUS_AUTHED;
		}
		else if (BCMASYNC_EVT_AUTH == event->evttype)
		{
			if (BCMASYNC_EVTSTATUS_SUCCESS == event->status)
			{
				new_status = BCM_WIFI_JOINSTATUS_AUTHED;
			}
			else
			{
				new_status = BCM_WIFI_JOINSTATUS_AUTH_FAILED;
				complete = true;
			}
		}
		
		vsfip_buffer_release(bcm_wifi->sdpcm.async_evt);
		bcm_wifi->sdpcm.async_evt = NULL;
		
		if (new_status || new_clr_status)
		{
			bcm_wifi->join_status[interface] |= new_status;
			bcm_wifi->join_status[interface] &= ~new_clr_status;
			if (bcm_wifi->join_status[interface] ==
					(BCM_WIFI_JOINSTATUS_AUTHED | BCM_WIFI_JOINSTATUS_LINKED |
					BCM_WIFI_JOINSTATUS_SECURED | BCM_WIFI_JOINSTATUS_COMPLETED))
			{
				complete = true;
			}
			if (status != NULL)
			{
				*status = new_status;
			}
			
			if (!complete)
			{
				vsfsm_post_evt_pending(pt->sm, BCMWIFI_EVT_REENTRY);
				vsfsm_pt_wfe(pt, BCMWIFI_EVT_REENTRY);
				if (status != NULL)
				{
					*status = 0;
				}
			}
			else
			{
				bcm_wifi->sdpcm.async_pending = false;
				break;
			}
		}
	}
	
	vsfsm_pt_end(pt);
	
	return VSFERR_NONE;
}

vsf_err_t bcm_wifi_leave(struct vsfsm_pt_t *pt, vsfsm_evt_t evt)
{
	struct bcm_wifi_t *bcm_wifi = (struct bcm_wifi_t *)pt->user_data;
	uint32_t interface = BCM_INTERFACE_STA;
	vsf_err_t err;
	
	vsfsm_pt_begin(pt);
	
	bcm_wifi->init_pt.user_data = bcm_wifi;
	bcm_wifi->init_pt.sm = pt->sm;
	
	bcm_sdpcm_get_ioctrl_buffer(&bcm_wifi->buffer, 0);
	if (NULL == bcm_wifi->buffer) return VSFERR_FAIL;
	bcm_wifi->init_pt.state = 0;
	vsfsm_pt_entry(pt);
	err = bcm_sdpcm_ioctrl(&bcm_wifi->init_pt, evt, interface,
							BCM_SDPCM_CMDTYPE_SET, BCMIOCTRL_DISASSOC,
							bcm_wifi->buffer, NULL);
	if (err > 0) return err;
	if (err < 0) {vsfip_buffer_release(bcm_wifi->buffer); return VSFERR_FAIL;}
	vsfip_buffer_release(bcm_wifi->buffer);
	
	bcm_wifi->join_status[interface] = 0;
	vsfsm_pt_end(pt);
	
	return VSFERR_NONE;
}

#endif
